package filter

import (
	gcontext "context"
	"errors"
	"fmt"
	"github.com/beego/beego/v2/core/logs"
	beego "github.com/beego/beego/v2/server/web"
	"github.com/beego/beego/v2/server/web/context"
	"github.com/casbin/casbin"
	"github.com/dgrijalva/jwt-go"
	"github.com/mitchellh/mapstructure"
	"net/http"
	"strings"
	"xpms/app/auth/groups"
	"xpms/cores"
)

// BasicAuthorizer stores the casbin handler
type BasicAuthorizer struct {
	enforcer *casbin.Enforcer
}

type Claims struct {
	Exp      float64
	Iat      int64
	Issuer   string
	UserId   string
	Account  string
	UserName string
	Departid string
	Role     []string
}

var text = `
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub == p.sub && keyMatch2(r.obj, p.obj) && regexMatch(r.act, p.act)
`
var e = casbin.NewEnforcer(casbin.NewModel(text))
var a = &BasicAuthorizer{enforcer: e}
var errResponse cores.Entity

// CheckPermission checks the user/method/path combination from the request.
// Returns true (permission granted) or false (permission forbidden)
func (a *BasicAuthorizer) CheckPermission(user string, r *http.Request) bool {
	method := r.Method
	path := r.URL.Path
	return a.enforcer.Enforce(user, path, method)
}

/**
  token 校验
*/
func Authorizer() beego.FilterFunc {
	return func(ctx *context.Context) {
		if HasIsOpenRestPermission(ctx) {
			return
		}
		secret, err := beego.AppConfig.String("secret")
		if err != nil {
			ctx.Output.Status = http.StatusForbidden
			ctx.Output.JSON(*errResponse.WithCode(http.StatusForbidden).WithMsg("Token secret:" + err.Error()), false, false)
			return
		}
		token, err := GetToken(ctx, secret)
		if err != nil {
			ctx.Output.Status = http.StatusForbidden
			ctx.Output.JSON(*errResponse.WithCode(http.StatusForbidden).WithMsg("Token is Error:" + err.Error()), false, false)
			return
		}
		claimsMap, ok := token.Claims.(jwt.MapClaims)
		if !ok {
			ctx.Output.Status = http.StatusForbidden
			ctx.Output.JSON(*errResponse.WithCode(http.StatusForbidden).WithMsg("Token is Error"), false, false)
			return
		}
		var claims Claims
		err = mapstructure.Decode(claimsMap, &claims)
		if err != nil {
			ctx.Output.Status = http.StatusForbidden
			ctx.Output.JSON(*errResponse.WithCode(http.StatusForbidden).WithMsg("权限映射值不对"), false, false)
			return
		}
		// 设置session 以备后面使用 ，目前是再登陆时候做session 如果部署两台机子后需要重新使用这种方式
		ctx.Input.CruSession.Set(gcontext.Background(), "userLogin", claims.UserId+"||"+claims.UserName+"||"+claims.Departid)
		ctx.Input.CruSession.Set(gcontext.Background(), "userGroupid", claims.Role)
		// 判断开放接口
		//HasRestPermission(ctx, &claims)

	}
}

/**
获取token
*/
func GetToken(ctx *context.Context, secret string) (t *jwt.Token, err error) {
	authString := ctx.Input.Header("Authorization")
	if authString == "" || strings.Split(authString, " ")[1] == "" {
		ctx.Output.Status = http.StatusForbidden
		ctx.Output.JSON(*errResponse.WithCode(http.StatusUnauthorized).WithMsg("AuthString invalid,Token:" + authString), false, false)
		return nil, errors.New(errResponse.Msg)
	}
	//beego.Debug("AuthString:", authString)

	kv := strings.Split(authString, " ")
	if len(kv) != 2 || kv[0] != "Bearer" {

		ctx.Output.Status = http.StatusForbidden
		ctx.Output.JSON(*errResponse.WithCode(http.StatusUnauthorized).WithMsg("AuthString invalid,Token:" + authString), false, false)
		return nil, errors.New(errResponse.Msg)
	}
	tokenString := kv[1]
	// Parse token
	token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("Unexpected signing method %v", token.Header["alg"])
		}

		return []byte(secret), nil
	})
	if err != nil {
		logs.Info("Parse token:", err)
		if ve, ok := err.(*jwt.ValidationError); ok {
			if ve.Errors&jwt.ValidationErrorMalformed != 0 {
				ctx.Output.Status = http.StatusForbidden
				ctx.Output.JSON(*errResponse.WithCode(http.StatusUnauthorized).WithMsg("That's not even a token"), false, false)
				return nil, errors.New(errResponse.Msg)
			} else if ve.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0 {
				ctx.Output.Status = http.StatusForbidden
				ctx.Output.JSON(*errResponse.WithCode(http.StatusUnauthorized).WithMsg("Token is either expired or not active yet"), false, false)
				return
			} else {
				ctx.Output.Status = http.StatusForbidden
				ctx.Output.JSON(*errResponse.WithCode(http.StatusUnauthorized).WithMsg("Couldn‘t handle this token"), false, false)

				return nil, errors.New(errResponse.Msg)
			}
		} else {
			ctx.Output.Status = http.StatusForbidden
			ctx.Output.JSON(*errResponse.WithCode(http.StatusUnauthorized).WithMsg("Parse token is error"), false, false)

			return nil, errors.New(errResponse.Msg)
		}
	}
	if !token.Valid {

		ctx.Output.Status = http.StatusForbidden
		ctx.Output.JSON(*errResponse.WithCode(http.StatusUnauthorized).WithMsg("Token invalid:" + tokenString), false, false)

		return nil, errors.New(errResponse.Msg)
	}
	//beego.Debug("Token:", token)
	return token, nil
}

/**
判断是否开放接口
*/
func HasIsOpenRestPermission(ctx *context.Context) bool {
	var openRest = []groups.GroupsUserPermission{
		{Ename: "/v1/sys/login", Method: "GET"},
	}
	for _, v := range openRest {
		e.AddPermissionForUser("1", v.Ename, v.Method)
	}
	if !a.CheckPermission("1", ctx.Request) {
		return false
	}
	return true
}

/**
判断是否有接口权限
*/
func HasRestPermission(ctx *context.Context, claims *Claims) {
	condArr := make(map[string]interface{})
	condArr["groupid"] = claims.Role
	condArr["nav"] = 2
	_, err, resources := groups.QueryGroupsRestPermission(condArr)
	if err != nil && resources == nil {
		ctx.Output.Status = http.StatusForbidden
		ctx.Output.JSON(*errResponse.WithCode(http.StatusForbidden).WithMsg("获取rest接口异常 is Error"), false, false)
		return
	}
	for _, v := range resources {
		e.AddPermissionForUser(claims.Account, v.Ename, v.Method)
	}
	if !a.CheckPermission(claims.Account, ctx.Request) {
		ctx.Output.Status = http.StatusUnauthorized
		ctx.Output.JSON(*errResponse.WithCode(http.StatusUnauthorized).WithMsg("Auth Fail"), false, false)
		return
	}
}
